import asyncio
import logging
from typing import List, Type

import numpy as np

from command import *
from config import config
from defines import *
from worker import HANDLERS, WORKER

from random import random, randint

from tcp_result_server import TCP_RESULT_SERVER

logger = logging.getLogger()

frame_t = np.dtype([
    ('hdr', 'S4'), 
    ('byte_len', 'u4'),
    ('os_ch', 'u1'),
    ('seq', 'u1'),
    ('data', 'i2', (2, 1024, 16*1024)),
    ])

def sim_wave_calc():
    data = np.empty((2, 1024, 16*1024), dtype='i2')
    seq_array = np.arange(16*1024, dtype='i2')

    for i in range(1024):
        data[0][i] = -seq_array if (i%2 == 1) else seq_array 
        data[1][i] = -seq_array[::-1] if (i%2 == 1) else seq_array[::-1] 

    frame = np.empty((1,), dtype=frame_t)
    frame['hdr'] = 'FHDR'[::-1]
    frame['byte_len'] = frame_t.itemsize
    frame['os_ch'] = 1
    frame['seq'] = 8
    frame['data'] = data
    
    return frame  
    

SIM_DATA = sim_wave_calc()



class FPGA_WORKER(WORKER):

    handlers = HANDLERS()

    def __init__(self, cfg:config) -> None:
        super().__init__()
        self.cfg = cfg

        self._sample_task = None

        self.tcp_server = TCP_RESULT_SERVER(cfg)

        self.tasks = [self.tcp_server.launch_server()]


    @property
    def thresholds(self) -> List[int]:
        return self.cfg.thresholds
    @thresholds.setter
    def thresholds(self, val):
        self.cfg.thresholds = val


    # @handlers(CMD.Type.SET_THRESHOLDS)
    # def set_thresholds(self, cmd:CMD_SET_THRESHOLDS):
    #     res = True
    #     for thres in cmd.thresholds:
    #         try:
    #             channel = thres["channel"]
    #             value = thres["value"]
    #             self.thresholds[channel] = value
    #             # TODO : Set to FPGA
    #         except KeyError as e:
    #             logging.exception(e)
    #             return False
    #     return res


    # @handlers(CMD.Type.GET_THRESHOLDS)
    # def get_thresholds(self, cmd:CMD_GET_THRESHOLDS):
    #     res = []
    #     for ch in cmd.channel:
    #         try:
    #             tresh = self.thresholds[ch]
    #             if tresh is None:
    #                 logger.error("Channel number %s out of bounder" % ch)
    #                 return []
    #             res += [tresh]
    #         except TypeError as e:
    #             logging.exception(e)
    #             return []
    #     return res

    async def __sample_data(self):
        while True:
            await asyncio.sleep(1)
            v = memoryview(SIM_DATA).cast('B')
            self.tcp_server.write(SIM_DATA)
            # self.tcp_server.write_view(v)

        
    @handlers(CMD.Type.START_SAMPLE)
    def start_sample(self, cmd):
        if self._sample_task is None or self._sample_task.done():
            self._sample_task = asyncio.create_task(self.__sample_data())
        return True


    @handlers(CMD.Type.STOP_SAMPLE)
    def stop_sample(self, cmd):
        if self._sample_task is not None:
            if not self._sample_task.done():
                self._sample_task.cancel()
            else:
                logger.warning("Execute stop_sample command while no sample started.")
        else:
            logger.warning("Execute stop_sample command while no sample started.")

        return True


    # @handlers(CMD.Type.GET_SPECTURE)
    # def get_specture(self, cmd:CMD_GET_SPECTURE):
        
    #     def sim_spec(wavelength):
    #         def gauss(x, a, b, c):
    #             return a*np.exp(-np.power((x-b), 2) / (2 * c**2))

    #         x = np.linspace(SPECTURE_MIN_WAVELENGTH, SPECTURE_MAX_WAVELENGTH, SPECTURE_LENGTH)
    #         spec = np.zeros_like(x)
    #         for i, xi in enumerate(x):
    #             spec[i] = gauss(xi, 6000, wavelength, 0.14) + 4000 + randint(0, 50)
    #         return spec.astype('u2')
        
    #     wl = cmd.channel + SPECTURE_MIN_WAVELENGTH + random()*0.25
    #     spec = sim_spec(wl)

    #     res = {"channel" : cmd.channel, "value" : spec.tolist()}
    #     return res
    
    @handlers(CMD.Type.GET_VERSION)
    def get_version(self, cmd):
        return self.cfg.software_version

    @handlers(CMD.Type.SET_ACC_TIMES)
    def set_acc_times(self, cmd):
        self.cfg.acc_times = cmd.acc_times
        return True

    @handlers(CMD.Type.SET_POS_LENGTH)
    def set_pos_length(self, cmd):
        self.cfg.pos_length = cmd.pos_length
        return True

    @handlers(CMD.Type.SET_SPEC_LENGTH)
    def set_spec_length(self, cmd):
        self.cfg.spec_length = cmd.spec_length
        return True

    @handlers(CMD.Type.SET_OPTICAL_SWITCH)
    def set_optical_switch(self, cmd):
        self.cfg.optical_sw_single = cmd.single
        self.cfg.optical_sw_ch = cmd.ch
        return True

    @handlers(CMD.Type.SET_AD_SAMPLE_RATE)
    def set_ad_sample_rate(self, cmd):
        self.cfg.ad_sample_rate = cmd.sample_rate
        return True

    @handlers(CMD.Type.SET_PULSE_WIDTH)
    def set_pulse_width(self, cmd):
        self.cfg.pulse_cycle_period = cmd.pulse_cycle_period
        self.cfg.pulse_5ns_cycles = cmd.pulse_5ns_cycles
        return True

    @handlers(CMD.Type.SET_INNER_TRIG_ENABLE)
    def set_inner_trig_enable(self, cmd):
        self.cfg.use_inner_trig = cmd.enable
        return True

    @handlers(CMD.Type.SET_INNER_SPEC)
    def set_inner_spec(self, cmd):
        self.cfg.inner_spec_cycle = cmd.spec_cycle
        self.cfg.inner_spec_width = cmd.spec_width
        return True

    @handlers(CMD.Type.SET_INNER_TRIG)
    def set_inner_trig(self, cmd):
        self.cfg.inner_trig_cycle = cmd.trig_cycle
        self.cfg.inner_trig_width = cmd.trig_width
        return True
    